using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Management;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Soap;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;

namespace OptimusPluginLib
{
  /// <summary>
  /// Base class for optimus plugin
  /// </summary>
  [ComVisible(true)]
  [ProgId("Optimus.WinampPlugin")]
  public class BaseOptimusPlugin : IOptimusPlugin
  {
    public const string ButtonActiveIconGuidProperty = "ActiveIcon";
    public const string ButtonIconsRegistryKeyProperty = @"HKEY_CURRENT_USER\Software\IOptimus\ButtonIcons";
    public const string ButtonInactiveIconGuidProperty = "InactiveIcon";
    public const string DirectoryToPlayProperty = "pathtoplay";
    public const string WinampProperty = "winamp";
    protected Image _activeIcon;
    private int _currentFrame;
    protected Image _inactiveIcon;
    protected Image _emptyDiscIcon;

    private Dictionary<Info, int> _infoBag;
    protected Dictionary<string, string> _propBag = new Dictionary<string, string>();
    private Dictionary<string, string> _sharedPropBag = new Dictionary<string, string>();

    private Process _winampProcess;
    private bool _discIsNoLoaded = false;

    public BaseOptimusPlugin()
    {
      _infoBag = new Dictionary<Info, int>();

      _infoBag.Add(Info.FUNCTIONS, 1);
      _infoBag.Add(Info.HAS_PROPERTIES, 1);
      _infoBag.Add(Info.HAS_SHARED_PROPERTIES, 1);
      _infoBag.Add(Info.KEY, WrapString("WinampPlugin"));
      _infoBag.Add(Info.NAME, WrapString("WinampPlugin"));
      _infoBag.Add(Info.DESC, WrapString("Media plugin for playing music."));
      _infoBag.Add(Info.AUTHOR, WrapString("Volodymyr Goncharov"));
      _infoBag.Add(Info.EMAIL, WrapString("volodymyr.goncharov@gmail.com"));
      _infoBag.Add(Info.ICON32, Resources.PluginIcon.Handle.ToInt32());

      _infoBag.Add(Info.DELAY, 350); // wonder how low you can go on the real hardware?

      _activeIcon = GetDefaultIcon(true);
      _inactiveIcon = GetDefaultIcon(false);

      AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

      _emptyDiscIcon = new Bitmap(Resources.InsertDisc, 96, 96);
    }

    private bool IsPlaying
    {
      get
      {
        if (_winampProcess == null)
        {
          return false;
        }

        bool hasExited;

        try
        {
          hasExited = _winampProcess.HasExited;
        }
        catch
        {
          hasExited = true;
        }

        if (hasExited)
        {
          _winampProcess = null;
          return false;
        }

        return true;
      }
    }

    #region IOptimusPlugin Members

    public int GetInfo(Info index)
    {
      Debug.WriteLine("GetInfo wants " + index);

      int value;

      if (_infoBag.TryGetValue(index, out value))
        return value;

      return 0;
    }

    public virtual void OnKeyDown()
    {
      if (!IsPlaying)
        StartWinamp();
      else
        CloseWinamp();
    }

    public virtual bool Paint(IntPtr HDC)
    {
      using (Graphics g = Graphics.FromHdc(HDC))
      {
        Image image;
        if (_discIsNoLoaded)
        {
          image = _emptyDiscIcon;
        }
        else
        {
          image = IsPlaying ? _activeIcon : _inactiveIcon;
        }

        Debug.Assert(image != null);

        if (image.RawFormat.Equals(ImageFormat.Gif))
        {
          FrameDimension dimension = new FrameDimension(image.FrameDimensionsList[0]);
          int frameCount;

          if ((frameCount = image.GetFrameCount(dimension)) > 1)
          {
            if (_currentFrame >= frameCount)
            {
              _currentFrame = 0;
            }

            image.SelectActiveFrame(dimension, _currentFrame);
            _currentFrame++;
          }
        }

        g.DrawImage(image, 0, 0);
      }

      return true;
    }

    public bool GetProperty(int rawIndex, out string name, out string value)
    {
      int bagIndex = -1;
      name = null;
      value = null;

      Dictionary<string, string> bag;

      // select which bag to pull from
      if ((rawIndex >= Utilities.PLUGIN_PROPERTIES) && (rawIndex < Utilities.PLUGIN_SHARED_PROPERTIES))
      {
        bag = _propBag;
        bagIndex = rawIndex - Utilities.PLUGIN_PROPERTIES;
        Debug.WriteLine("GetProperty (PLUGIN_PROPERTIES) : " + bagIndex);
      }
      else if (rawIndex >= Utilities.PLUGIN_SHARED_PROPERTIES)
      {
        bag = _sharedPropBag;
        bagIndex = rawIndex - Utilities.PLUGIN_SHARED_PROPERTIES;
        Debug.WriteLine("GetProperty (PLUGIN_SHARED_PROPERTIES) : " + bagIndex);
      }
      else
        return false; // unexpected propIndex < 256, just bail

      if (bagIndex >= bag.Count)
        return false; // signal that we're out of items

      int curIndex = 0;

      // sucky, but it works
      foreach (KeyValuePair<string, string> kvp in bag)
      {
        if (bagIndex == curIndex++)
        {
          name = kvp.Key;
          value = kvp.Value;
          return true;
        }
      }

      return false; // catchall
    }

    public bool OnEvent(OptimusEvent evt)
    {
      Debug.WriteLine("Event " + evt + " fired");

      return true;
    }

    public bool SetProperty(string name, string value)
    {
      if (!string.IsNullOrEmpty(name))
      {
        _propBag[name] = value;

        if (name == ButtonActiveIconGuidProperty)
        {
          _activeIcon = ReadButtonIcon(true);
        }

        if (name == ButtonInactiveIconGuidProperty)
        {
          _inactiveIcon = ReadButtonIcon(false);
        }
      }

      return true;
    }

    /// <summary>
    /// Shows a window with plugin properties
    /// </summary>
    public virtual bool ShowProperties(IntPtr hwnd)
    {
      string pathToWinamp = null, directoryToPlay = null;
      _propBag.TryGetValue(WinampProperty, out pathToWinamp);
      _propBag.TryGetValue(DirectoryToPlayProperty, out directoryToPlay);

      using (PluginOptionsForm form = new PluginOptionsForm(_activeIcon, _inactiveIcon,
                                                            pathToWinamp, directoryToPlay))
      {
        if (form.ShowDialog() == DialogResult.OK)
        {
          Guid guid = GetIconGuid(true);
          StoreButtonIcon(guid, form.ActiveIcon);
          _propBag[ButtonActiveIconGuidProperty] = guid.ToString();

          guid = GetIconGuid(false);
          StoreButtonIcon(guid, form.InactiveIcon);
          _propBag[ButtonInactiveIconGuidProperty] = guid.ToString();

          _propBag[WinampProperty] = form.PathToWinamp;
          _propBag[DirectoryToPlayProperty] = form.DirectoryToPlay;

          _activeIcon = ReadButtonIcon(true);
          _inactiveIcon = ReadButtonIcon(false);
        }
      }

      return true;
    }

    #endregion

    private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
      MessageHelper.ShowError(string.Format("{0}", e.ExceptionObject));
    }

    private Guid GetIconGuid(bool IsActiveIcon)
    {
      string bagKey = IsActiveIcon ? ButtonActiveIconGuidProperty : ButtonInactiveIconGuidProperty;
      string strGuid;
      _propBag.TryGetValue(bagKey, out strGuid);

      return string.IsNullOrEmpty(strGuid) ? Guid.NewGuid() : new Guid(strGuid);
    }

    protected Bitmap GetDefaultIcon(bool IsActiveIcon)
    {
      Bitmap bitmap = IsActiveIcon ? Resources.On : Resources.Off;
      return new Bitmap(bitmap, 96, 96);
    }

    /// <summary>
    /// Reads ButtonIcon from registry
    /// </summary>
    public Image ReadButtonIcon(bool IsActiveIcon)
    {
      string guid;
      string guidProperty = IsActiveIcon ? ButtonActiveIconGuidProperty : ButtonInactiveIconGuidProperty;

      if (_propBag.TryGetValue(guidProperty, out guid))
      {
        string serializedIcon = Registry.GetValue(ButtonIconsRegistryKeyProperty, guid, null) as string;

        if (!string.IsNullOrEmpty(serializedIcon))
        {
          Byte[] bytes = Encoding.Default.GetBytes(serializedIcon);

          using (MemoryStream stream = new MemoryStream(bytes, 0, bytes.Length))
          {
            SoapFormatter formatter = new SoapFormatter();

            return formatter.Deserialize(stream) as Image;
          }
        }
      }

      return GetDefaultIcon(IsActiveIcon);
    }

    /// <summary>
    /// Stores Icon to registry and associates it with guid.
    /// </summary>
    public static void StoreButtonIcon(Guid IconGuid, Image Icon)
    {
      SoapFormatter formatter = new SoapFormatter();
      using (MemoryStream stream = new MemoryStream())
      {
        string serializedIcon = string.Empty;

        if (Icon != null)
        {
          formatter.Serialize(stream, Icon);

          StreamReader reader = new StreamReader(stream);
          stream.Position = 0;

          serializedIcon = reader.ReadToEnd();
        }

        Registry.SetValue(ButtonIconsRegistryKeyProperty, IconGuid.ToString(), serializedIcon,
                          RegistryValueKind.String);
      }
    }

    #region Helpers

    public static int WrapString(string ManagedString)
    {
      Debug.WriteLine("Returning \"" + ManagedString + "\" to unmanaged code.");
      return Marshal.StringToHGlobalAnsi(ManagedString).ToInt32();
    }

    private void TerminateProcess(Process Process)
    {
      using (Process)
      {
        if (!Process.CloseMainWindow())
          Process.Kill();

        Process.Close();
      }
    }

    private void CloseWinamp()
    {
      if (!IsPlaying)
      {
        return;
      }

      TerminateProcess(_winampProcess);
    }

    private void StartWinamp()
    {
      string winamp, directory;

      if (!_propBag.TryGetValue(WinampProperty, out winamp) ||
          !_propBag.TryGetValue(DirectoryToPlayProperty, out directory))
      {
        MessageHelper.ShowMessage("Could not open a directory to play. Options are not specified.");
        return;
      }

      // checks if CD should play and a disc exists
      char drive = char.ToUpper(directory[0]);
      SelectQuery driveQuery =
        new SelectQuery("select name, volumename, volumeserialnumber from win32_logicaldisk where drivetype=5");
      ManagementObjectSearcher searcher = new ManagementObjectSearcher(driveQuery);

      foreach (ManagementObject mo in searcher.Get())
      {
        char foundDrive = char.ToUpper(mo["name"].ToString()[0]);

        // If both properties are null I suppose there's no CD
        if (drive == foundDrive &&
            mo["volumename"] == null &&
            mo["volumeserialnumber"] == null)
        {
          _discIsNoLoaded = true;
          return;
        }
      }
      _discIsNoLoaded = false;

      // closes running instances of winamp
      Process[] processes = Process.GetProcessesByName("winamp");
      if (processes.Length != 0)
      {
        foreach (Process process in processes)
        {
          TerminateProcess(process);
        }
      }

      try
      {
        _winampProcess = Process.Start(winamp,
                                       string.Format(@"""{0}""", directory));
      }
      catch (Win32Exception)
      {
        MessageHelper.ShowError(string.Format("Winamp executable is not found. File name: '{0}'",
                                              winamp));
      }
      catch (Exception e)
      {
        MessageHelper.ShowError(e);
      }
    }

    #endregion
  }
}